package cadtoolbox.model;



import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import javax.swing.ProgressMonitor;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeModel;
import javax.swing.tree.DefaultTreeModel;

import cadtoolbox.optimizer.Parameter;

import org.apache.commons.collections15.Factory;

import cadtoolbox.utils.MyPair;
import cadtoolbox.utils.MyWorker;

import edu.uci.ics.jung.graph.AbstractGraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.graph.util.Pair;
import cadtoolbox.graphical.EdgeFactory;
import cadtoolbox.graphical.VertexFactory;
import cadtoolbox.utils.Undoable;

/**
 * An implementation of <code>Graph</code> that is suitable for sparse graphs
 * and permits directed, undirected, and parallel edges.
 */

public class OligoGraph<V, E> extends AbstractGraph<V, E> implements Serializable, Undoable, Cloneable{

	/**
	 * 
	 */
	private static final long serialVersionUID = 6175408279157831804L;

	public boolean saturableExo = true;
	public boolean exoSaturationByFreeTemplates = true; // only if saturableExo
	public boolean saturablePoly = true;
    public boolean saturableNick = true; // not implemented yet with Mathematica
    public boolean selfStart = false;
    public boolean coupling = true;
    
    
    private LinkedHashSet<V> plottedSeqs;
	private LinkedHashSet<V> notPlottedSeqs;
	private Stack<Undoable> edits = new Stack<Undoable>();
	public DefaultTreeModel optimizable = new DefaultTreeModel(new DefaultMutableTreeNode("Parameters"));
	
	public boolean saved = true;
	
	/**
	 * Returns a {@code Factory} that creates an instance of this graph type.
	 * 
	 * @param <V>
	 *            the vertex type for the graph factory
	 * @param <E>
	 *            the edge type for the graph factory
	 */
	public static <V, E> Factory<Graph<V, E>> getFactory() {
		return new Factory<Graph<V, E>>() {
			public Graph<V, E> create() {
				return new OligoGraph<V, E>();
			}
		};
	}

	// TODO: refactor internal representation: right now directed edges each
	// have two references (in vertices and directedEdges)
	// and undirected also have two (incoming and outgoing).
	protected Map<V, Pair<Set<E>>> vertices; // Map of vertices to Pair of
	// adjacency sets {incoming,
	// outgoing}
	protected Map<E, Pair<V>> edges; // Map of edges to incident vertex pairs
	/**
	 * The second element of the pair is a representation of the inhibition arrow.
	 * It is used by the OligoRenderer to discriminate between a template and
	 * its inhibition arrow. The AnimatedSequences class also uses it to find the correct
	 * color to display.
	 */
	protected Map<E, MyPair<V, E>> inhibitions;
	public Map<V,E> inhibitors;
	protected Set<E> directedEdges;
	public Map<V, Double> K;
	protected Map<E,Double> templateConcentrations;
	protected Set<V> selectedVertices;
	protected ArrayList<V> availableVertices;
	
	protected VertexFactory<V> vertexFactory;
	protected EdgeFactory<V,E> edgeFactory;
	
	public cadtoolbox.utils.PlotExpData autoplot;

	

	/**
	 * Creates a new instance.
	 */
	public OligoGraph() {
		vertices = new LinkedHashMap<V, Pair<Set<E>>>();
		edges = new HashMap<E, Pair<V>>();
		directedEdges = new HashSet<E>();
		inhibitions = new HashMap<E, MyPair<V, E>>();
		inhibitors = new HashMap<V,E>();
		K = new HashMap<V, Double>();
		templateConcentrations = new HashMap<E,Double>();
		selectedVertices = new LinkedHashSet<V>();
		availableVertices = new ArrayList<V>();
		plottedSeqs = new LinkedHashSet<V>();
		notPlottedSeqs = new LinkedHashSet<V>();
		optimizable.insertNodeInto(new DefaultMutableTreeNode("Sequence dissociation constants"), (MutableTreeNode) optimizable.getRoot(), 0);
		optimizable.insertNodeInto(new DefaultMutableTreeNode("Initial sequence concentrations"), (MutableTreeNode) optimizable.getRoot(), 1);
		optimizable.insertNodeInto(new DefaultMutableTreeNode("Template concentrations"), (MutableTreeNode) optimizable.getRoot(), 2);
		
	}

	public void initFactories(VertexFactory<V> vert, EdgeFactory<V,E> ed){
		this.vertexFactory = vert;
		this.edgeFactory = ed;
	}
	
	public Collection<E> getEdges() {
		return Collections.unmodifiableCollection(edges.keySet());
	}

	public Collection<V> getVertices() {
		return Collections.unmodifiableCollection(vertices.keySet());
	}

	public Collection<E> getInhibitions() {
		return (Collection<E>) inhibitions.keySet();
	}

	public MyPair<V, E> getInhibition(E e) {
		MyPair<V, E> inh = inhibitions.get(e);
		return inh;
	}

	public boolean containsVertex(V vertex) {
		return vertices.keySet().contains(vertex);
	}

	public boolean containsEdge(E edge) {
		return edges.keySet().contains(edge);
	}

	protected Collection<E> getIncoming_internal(V vertex) {
		return vertices.get(vertex).getFirst();
	}

	protected Collection<E> getOutgoing_internal(V vertex) {
		return vertices.get(vertex).getSecond();
	}

	public boolean addVertex(V vertex) {
		if (vertex == null) {
			throw new IllegalArgumentException("vertex may not be null");
		}
		if (!vertices.containsKey(vertex)) {
	        final V newVertex = vertex;
			vertices.put(newVertex, new Pair<Set<E>>(new HashSet<E>(),
					new HashSet<E>()));
			plottedSeqs.add(newVertex);
			addVertexOptimizableParameters(newVertex);
			saved = false;
			edits.add(new Undoable(){

				@Override
				public void undo() {
					removeVertex(newVertex);
				}
				
			});
			//this.replot(); Nothing really changed. Also, no K yet
			return true;
		} else {
			return false;
		}
	}

	protected void addVertexOptimizableParameters(final V newVertex) {
		Parameter<V,E> seqK = new Parameter<V,E>(0,0,0,newVertex){
			
			V mySeq;
			
			@Override
			protected void init(){
				this.mySeq = newVertex;
				this.currentValue = 0;
				this.minValue = 0;
				this.maxValue = 100;
				this.name = mySeq.toString();
			}
			
			@Override
			protected void updateTarget(OligoGraph<V,E> graph) {
				ArrayList<V> vert = new ArrayList<V>(graph.vertices.keySet());
				int index = -1;
				for(int i = 0; i<vert.size(); i++){
					if(vert.get(i).equals(mySeq)){
						index = i;
						break;
					}
				}
				graph.setK(vert.get(index), currentValue);
			}
		};
		Parameter<V,E> seqC = new Parameter<V,E>(0,0,0,newVertex){
			
			SequenceVertex mySeq;
			
			@Override
			protected void init(){
				this.mySeq = (SequenceVertex) newVertex; //TODO: I have to do something about this
				this.currentValue = ((SequenceVertex)newVertex).initialConcentration;
				this.minValue = 0;
				this.maxValue = 100;
				this.name = mySeq.toString();
			}
			
			@Override
			protected void updateTarget(OligoGraph<V,E> graph) {
				ArrayList<V> vert = new ArrayList<V>(graph.vertices.keySet());
				int index = -1;
				for(int i = 0; i<vert.size(); i++){
					if(vert.get(i).equals(mySeq)){
						index = i;
						break;
					}
				}
				((SequenceVertex) vert.get(index)).setConcentration(currentValue);
			}
		};
		optimizable.insertNodeInto(new DefaultMutableTreeNode(seqK), (MutableTreeNode) optimizable.getChild(optimizable.getRoot(), 0), ((DefaultMutableTreeNode) optimizable.getChild(optimizable.getRoot(), 0)).getChildCount());
		optimizable.insertNodeInto(new DefaultMutableTreeNode(seqC), (MutableTreeNode) optimizable.getChild(optimizable.getRoot(), 1), ((DefaultMutableTreeNode) optimizable.getChild(optimizable.getRoot(), 1)).getChildCount());
	}
	
	protected void removeVertexOptimizableParameters(V newVertex) {
		Enumeration<DefaultMutableTreeNode> it = ((MutableTreeNode) optimizable.getChild(optimizable.getRoot(), 0)).children();
		while(it.hasMoreElements()){
			DefaultMutableTreeNode next = it.nextElement();
			if(((Parameter)next.getUserObject()).target.equals(newVertex)){
				next.removeFromParent();
				break;
			}
		}
        it = ((MutableTreeNode) optimizable.getChild(optimizable.getRoot(), 1)).children();
		while(it.hasMoreElements()){
			DefaultMutableTreeNode next = it.nextElement();
			if(((Parameter)next.getUserObject()).target.equals(newVertex)){
				next.removeFromParent();
				break;
			}
		}
	}

	public boolean removeVertex(V vertex) {
		if (!containsVertex(vertex))
			return false;

		final V finalVertex = vertex;
		final boolean plotted = plottedSeqs.contains(finalVertex);
		final boolean wasInhibitor = inhibitors.containsKey(vertex);
		removeVertexOptimizableParameters(finalVertex);
		if(plotted){
			plottedSeqs.remove(vertex);
		} else {
			notPlottedSeqs.remove(vertex);
		}
//		edits.add(new Undoable(){
//
//			@Override
//			public void undo() {
//				vertices.put(finalVertex, new Pair<Set<E>>(new HashSet<E>(),
//						new HashSet<E>()));
//				if(plotted){
//					plottedSeqs.add(finalVertex);
//				} else {
//					notPlottedSeqs.add(finalVertex);
//				}
//				
//			}
//			
//		});
		
		
		if(wasInhibitor){
			inhibitions.remove(inhibitors.get(vertex));
			inhibitors.remove(vertex);
		} else {
		Set<E> inhibitions = new HashSet<E>(this.getInhibitions());
		
		for (E inhib : inhibitions){
			if(this.getEndpoints(inhib).contains(vertex)){
				removeVertex(this.getInhibition(inhib).getLeft());
				break;
			}
		}
		}
		// copy to avoid concurrent modification in removeEdge
		Set<E> incident = new HashSet<E>(getIncoming_internal(vertex));
		incident.addAll(getOutgoing_internal(vertex));

		for (E edge : incident)
			removeEdge(edge);
		
		vertices.remove(vertex);
		availableVertices.add(vertex);
		//this.replot();
		return true;
	}
	
	public V popAvailableVertex(){
		if (availableVertices.size() == 0){
			return null;
		}
		V v = availableVertices.get(0);
		availableVertices.remove(v);
		return v;
	}

	public boolean addInhibition(E e1, V v) {
		MyPair<V, E> inh = new MyPair<V, E>(v, this.edgeFactory.inhibitorName(e1));
		inhibitions.put(e1, inh);
		inhibitors.put(v,e1);
		//This always come with an operation performing a replot
		return true;
	}

	public boolean addActivation(E e, V v1, V v2) {
		final E finalE = e;
		this.addEdge(e, v1, v2, EdgeType.DIRECTED);
		this.templateConcentrations.put(e, 10.0);
		Parameter<V,E> temp = new Parameter<V,E>(10,0,100,e){
			
			
			
			@Override
			protected void init(){
				this.name = target.toString();
			}
			
			@Override
			protected void updateTarget(OligoGraph<V,E> graph) {
				graph.templateConcentrations.put((E)target, (Double) currentValue);
			}
		};
		optimizable.insertNodeInto(new DefaultMutableTreeNode(temp), (MutableTreeNode) optimizable.getChild(optimizable.getRoot(), 2), ((DefaultMutableTreeNode) optimizable.getChild(optimizable.getRoot(), 2)).getChildCount());
		saved = false;
		this.edits.add(new Undoable(){

			@Override
			public void undo() {
				removeEdge(finalE);
				replot();
			}
			
		});
		this.replot();
		return true;
	}
	
	public boolean addActivation(E e, V v1, V v2, double conc) {
		final E finalE = e;
		this.addEdge(e, v1, v2, EdgeType.DIRECTED);
		this.templateConcentrations.put(e, conc);
		Parameter<V,E> temp = new Parameter<V,E>(conc,0,100,e){
			
			
			
			@Override
			protected void init(){
				this.name = target.toString();
			}
			
			@Override
			protected void updateTarget(OligoGraph<V,E> graph) {
				graph.templateConcentrations.put((E)target, (Double) currentValue);
			}
		};
		final DefaultMutableTreeNode dmtn = new DefaultMutableTreeNode(temp);
		optimizable.insertNodeInto(new DefaultMutableTreeNode(temp), (MutableTreeNode) optimizable.getChild(optimizable.getRoot(), 2), ((DefaultMutableTreeNode) optimizable.getChild(optimizable.getRoot(), 2)).getChildCount());
		saved = false;
		this.edits.add(new Undoable(){

			@Override
			public void undo() {
				removeEdge(finalE);
				optimizable.removeNodeFromParent(dmtn);
				replot();
			}
			
		});
		this.replot();
		return true;
	}

	@Override
	public boolean addEdge(E edge, Pair<? extends V> endpoints,
			EdgeType edgeType) {

		Pair<V> new_endpoints = getValidatedEndpoints(edge, endpoints);
		if (new_endpoints == null)
			return false;

		V v1 = new_endpoints.getFirst();
		V v2 = new_endpoints.getSecond();

		if (!vertices.containsKey(v1))
			this.addVertex(v1);

		if (!vertices.containsKey(v2))
			this.addVertex(v2);

		vertices.get(v1).getSecond().add(edge);
		vertices.get(v2).getFirst().add(edge);
		edges.put(edge, new_endpoints);
		if (edgeType == EdgeType.DIRECTED) {
			directedEdges.add(edge);
		} else {
			vertices.get(v1).getFirst().add(edge);
			vertices.get(v2).getSecond().add(edge);
		}
		return true;
	}

	public boolean removeEdge(E edge) {
		if (!containsEdge(edge)) {
			return false;
		}

		Pair<V> endpoints = getEndpoints(edge);
		V v1 = endpoints.getFirst();
		V v2 = endpoints.getSecond();

		// remove edge from incident vertices' adjacency sets
		vertices.get(v1).getSecond().remove(edge);
		vertices.get(v2).getFirst().remove(edge);

		if (directedEdges.remove(edge) == false) {

			// its an undirected edge, remove the other ends
			vertices.get(v2).getSecond().remove(edge);
			vertices.get(v1).getFirst().remove(edge);
		}
		edges.remove(edge);
		templateConcentrations.remove(edge);
		Enumeration<DefaultMutableTreeNode> it = ((MutableTreeNode) optimizable.getChild(optimizable.getRoot(), 2)).children();
		while(it.hasMoreElements()){
			DefaultMutableTreeNode next = it.nextElement();
			if(((Parameter)next.getUserObject()).target.equals(edge)){
				next.removeFromParent();
				break;
			}
		}
		
		this.replot();
		return true;
	}

	public Collection<E> getInEdges(V vertex) {
		if (!containsVertex(vertex))
			return null;
		return Collections.unmodifiableCollection(vertices.get(vertex)
				.getFirst());
	}

	public Collection<E> getOutEdges(V vertex) {
		if (!containsVertex(vertex))
			return null;
		Collection<E> c = Collections.unmodifiableCollection(vertices.get(vertex)
				.getSecond());
		return c;
	}

	// TODO: this will need to get changed if we modify the internal
	// representation
	public Collection<V> getPredecessors(V vertex) {
		if (!containsVertex(vertex))
			return null;

		Set<V> preds = new HashSet<V>();
		for (E edge : getIncoming_internal(vertex)) {
			if (getEdgeType(edge) == EdgeType.DIRECTED) {
				preds.add(this.getSource(edge));
			} else {
				preds.add(getOpposite(vertex, edge));
			}
		}
		return Collections.unmodifiableCollection(preds);
	}

	// TODO: this will need to get changed if we modify the internal
	// representation
	public Collection<V> getSuccessors(V vertex) {
		if (!containsVertex(vertex))
			return null;
		Set<V> succs = new HashSet<V>();
		for (E edge : getOutgoing_internal(vertex)) {
			if (getEdgeType(edge) == EdgeType.DIRECTED) {
				succs.add(this.getDest(edge));
			} else {
				succs.add(getOpposite(vertex, edge));
			}
		}
		return Collections.unmodifiableCollection(succs);
	}

	public Collection<V> getNeighbors(V vertex) {
		if (!containsVertex(vertex))
			return null;
		Collection<V> out = new HashSet<V>();
		out.addAll(this.getPredecessors(vertex));
		out.addAll(this.getSuccessors(vertex));
		return out;
	}

	public Collection<E> getIncidentEdges(V vertex) {
		if (!containsVertex(vertex))
			return null;
		Collection<E> out = new HashSet<E>();
		out.addAll(this.getInEdges(vertex));
		out.addAll(this.getOutEdges(vertex));
		return out;
	}

	@Override
	public E findEdge(V v1, V v2) {
		if (!containsVertex(v1) || !containsVertex(v2))
			return null;
		for (E edge : getOutgoing_internal(v1))
			if (this.getOpposite(v1, edge).equals(v2))
				return edge;

		return null;
	}

	public Pair<V> getEndpoints(E edge) {
		return edges.get(edge);
	}

	public V getSource(E edge) {
		if (directedEdges.contains(edge)) {
			return this.getEndpoints(edge).getFirst();
		}
		return null;
	}

	public V getDest(E edge) {
		if (directedEdges.contains(edge)) {
			return this.getEndpoints(edge).getSecond();
		}
		return null;
	}

	public boolean isSource(V vertex, E edge) {
		if (!containsEdge(edge) || !containsVertex(vertex))
			return false;
		return getSource(edge).equals(vertex);
	}

	public boolean isDest(V vertex, E edge) {
		if (!containsEdge(edge) || !containsVertex(vertex))
			return false;
		return getDest(edge).equals(vertex);
	}

	public EdgeType getEdgeType(E edge) {
		return directedEdges.contains(edge) ? EdgeType.DIRECTED
				: EdgeType.UNDIRECTED;
	}

	@SuppressWarnings("unchecked")
	public Collection<E> getEdges(EdgeType edgeType) {
		if (edgeType == EdgeType.DIRECTED) {
			return Collections.unmodifiableSet(this.directedEdges);
		} else if (edgeType == EdgeType.UNDIRECTED) {
			Collection<E> edges = new HashSet<E>(getEdges());
			edges.removeAll(directedEdges);
			return edges;
		} else {
			return Collections.EMPTY_SET;
		}

	}

	public int getEdgeCount() {
		return edges.keySet().size();
	}

	public int getVertexCount() {
		return vertices.keySet().size();
	}

	public int getEdgeCount(EdgeType edge_type) {
		return getEdges(edge_type).size();
	}

	public EdgeType getDefaultEdgeType() {
		return EdgeType.DIRECTED;
	}

	public double getK(V v) {
		return K.get(v);
	}
	
	public void setK(V v, double k){
		final V finalV = v;
		final Double previousK = K.get(v);
		K.put(v, k);
		Parameter tempSeqK = null;
		if(optimizable.getChildCount(optimizable.getRoot())==0)
			return;
		Enumeration<DefaultMutableTreeNode> it = ((MutableTreeNode) optimizable.getChild(optimizable.getRoot(), 0)).children();
		while(it.hasMoreElements()){
			DefaultMutableTreeNode next = it.nextElement();
			if(((Parameter) next.getUserObject()).target.equals(finalV)){
				tempSeqK = (Parameter) next.getUserObject();
				break;
			}
		}
		if(tempSeqK != null){
			tempSeqK.currentValue = k;
			//System.out.println("Succes param");
		}
		final Parameter seqK = tempSeqK;
		saved = false;
		edits.add(new Undoable(){
	
			@Override
			public void undo() {
				if(previousK!=null){
					K.put(finalV, previousK);
					if(seqK !=null){
						seqK.currentValue = previousK;
					}
				} else {
					K.remove(finalV); //defensive programming. K should not be set by this function at creation
				}
				replot();
			}
			
		});
		this.replot();
	}
	
	public Set<V> getSelected(){
		return this.selectedVertices;
	}
	public void setSelected(Set<V> newselected){
		this.selectedVertices = newselected;
	}

	public EdgeFactory<V,E> getEdgeFactory(){
		return edgeFactory;
	}
	
	public VertexFactory<V> getVertexFactory(){
		return vertexFactory;
	}
	
	public boolean isInhibitor(V v){
		return this.inhibitors.containsKey(v);
	}
	
	public E getInhibitedEdge(V v){
		return this.inhibitors.get(v);
	}
	
	public Double getTemplateConcentration(E e){
		return templateConcentrations.get(e);
	}
	
	public void setTemplateConcentration(E e, Double d){
		//this.savePreviousState();
		final Double finalD = templateConcentrations.get(e);
		final E finalE = e;
		saved = false;
		edits.add(new Undoable(){

			@Override
			public void undo() {
				templateConcentrations.remove(finalE);
				if(finalD!=null){
				templateConcentrations.put(finalE,finalD);
				}
				replot();
			}
			
		});
		this.templateConcentrations.remove(e);
		this.templateConcentrations.put(e, d);
		this.replot();
	}
	
	public Collection<E> getInhibitableTemplates(){
		ArrayList<E> temps = new ArrayList<E>();
		for (E e: this.getEdges()){
			if (this.inhibitors != null&&!this.inhibitors.containsKey(getDest(e))&&!this.inhibitions.containsKey(e)){
				temps.add(e);
			}
		}
		return temps;
	}
	
	public void totalReset(){
		vertices = new HashMap<V, Pair<Set<E>>>();
		edges = new HashMap<E, Pair<V>>();
		directedEdges = new HashSet<E>();
		inhibitions = new HashMap<E, MyPair<V, E>>();
		inhibitors = new HashMap<V,E>();
		K = new HashMap<V, Double>();
		templateConcentrations = new HashMap<E,Double>();
		selectedVertices = new HashSet<V>();
		availableVertices = new ArrayList<V>();
		plottedSeqs = new LinkedHashSet<V>();
		notPlottedSeqs = new LinkedHashSet<V>();
		saved = true;
		edits = new Stack<Undoable>();
		optimizable = new DefaultTreeModel(new DefaultMutableTreeNode("Parameters"));
		optimizable.insertNodeInto(new DefaultMutableTreeNode("Sequence dissociation constants"), (MutableTreeNode) optimizable.getRoot(), 0);
		optimizable.insertNodeInto(new DefaultMutableTreeNode("Initial sequence concentrations"), (MutableTreeNode) optimizable.getRoot(), 1);
		optimizable.insertNodeInto(new DefaultMutableTreeNode("Template concentrations"), (MutableTreeNode) optimizable.getRoot(), 2);
		
	}
	
	public void addPlottedSeq(V seq){
		if(vertices.containsKey(seq) && !plottedSeqs.contains(seq)){
			plottedSeqs.add(seq);
			notPlottedSeqs.remove(seq);
			this.replot();
		}
	}
	
	public void removePlottedSeq(V seq){
		if(vertices.containsKey(seq) && plottedSeqs.contains(seq)){
			plottedSeqs.remove(seq);
			notPlottedSeqs.add(seq);
			this.replot();
		}
	}
	
	public LinkedHashSet<V> getPlottedSeqs(){
		return this.plottedSeqs;
	}
	
	public LinkedHashSet<V> getNotPlottedSeqs(){
		return this.notPlottedSeqs;
	}
	
	// Everytime there is a modification
	public void replot() {
		if(this.autoplot!= null && cadtoolbox.utils.PlotExpData.autoplot){
		OligoSystem model = new OligoSystem(this);
		double[][] results = model.calculateTimeSeries(null);
		this.autoplot.updatePlot(results, model.getActivity(), 0, 1, model.giveNames());
		}
	}

	public void undo() {
		if(!edits.isEmpty()){
			edits.pop().undo(); //the undoable should replot themshelves
			saved = false;
		}
	}
	
	public boolean undoListEmpty(){
		return edits.isEmpty();
	}
	
	public TreeModel getOptimizable(){
		return optimizable;
	}
	
	public void addUndoable(Undoable undo){
		this.edits.add(undo);
		saved = false;
	}
	
	/**
	 * Used to evolve the graph's parameters
	 */
	public Object clone(){
		final OligoGraph<V,E> clone;
		try {
			clone = (OligoGraph<V, E>) super.clone();
			clone.vertices = new HashMap<V, Pair<Set<E>>>();
			HashMap<V,V> equivalence = new HashMap<V,V>();
			clone.edges = new HashMap<E, Pair<V>>();
			
			clone.inhibitions = new HashMap<E, MyPair<V, E>>();
			clone.inhibitors = new HashMap<V,E>();
			clone.K = new HashMap<V, Double>();
			clone.plottedSeqs = new LinkedHashSet<V>();
			clone.optimizable = new DefaultTreeModel(new DefaultMutableTreeNode("Parameters"));
			clone.optimizable.insertNodeInto(new DefaultMutableTreeNode("Sequence dissociation constants"), (MutableTreeNode) clone.optimizable.getRoot(), 0);
			clone.optimizable.insertNodeInto(new DefaultMutableTreeNode("Initial sequence concentrations"), (MutableTreeNode) clone.optimizable.getRoot(), 1);
			clone.optimizable.insertNodeInto(new DefaultMutableTreeNode("Template concentrations"), (MutableTreeNode) clone.optimizable.getRoot(), 2);

			for(V v : this.vertices.keySet()){
				final V newV = this.getVertexFactory().copy(v);
				((SequenceVertex) newV).setConcentration(((SequenceVertex) v).initialConcentration);
				((SequenceVertex) newV).initialConcentration = (((SequenceVertex) v).initialConcentration);
				equivalence.put(v, newV);
				clone.vertices.put(newV, this.vertices.get(v)); // This is fine, since we don't evolve the topology
				clone.K.put(newV, new Double(this.K.get(v)));
				if(this.inhibitors.containsKey(v)){
					E inhibited = this.inhibitors.get(v);
					clone.inhibitors.put(newV, inhibited);
					clone.inhibitions.put(inhibited, new MyPair<V,E>(newV,inhibited));
				}
				Parameter<V,E> seqK = new Parameter<V,E>(0,0,0,newV){
					
					V mySeq;
					
					@Override
					protected void init(){
						this.mySeq = newV;
						this.currentValue = clone.K.get(newV);
						this.minValue = 0;
						this.maxValue = 100;
						this.name = mySeq.toString();
					}
					
					@Override
					protected void updateTarget(OligoGraph<V,E> graph) {
						ArrayList<V> vert = new ArrayList<V>(graph.vertices.keySet());
						int index = -1;
						for(int i = 0; i<vert.size(); i++){
							if(vert.get(i).equals(mySeq)){
								index = i;
								break;
							}
						}
						clone.setK(vert.get(index), currentValue);
					}
				};
				Parameter<V,E> seqC = new Parameter<V,E>(0,0,0,newV){
					
					SequenceVertex mySeq;
					
					@Override
					protected void init(){
						this.mySeq = (SequenceVertex) newV; //TODO: I have to do something about this
						this.currentValue = ((SequenceVertex)newV).initialConcentration;
						this.minValue = 0;
						this.maxValue = 100;
						this.name = mySeq.toString();
					}
					
					@Override
					protected void updateTarget(OligoGraph<V,E> graph) {
						ArrayList<V> vert = new ArrayList<V>(clone.vertices.keySet());
						int index = -1;
						for(int i = 0; i<vert.size(); i++){
							if(vert.get(i).equals(mySeq)){
								index = i;
								break;
							}
						}
						((SequenceVertex) vert.get(index)).setConcentration(currentValue);
					}
				};
				clone.optimizable.insertNodeInto(new DefaultMutableTreeNode(seqK), (MutableTreeNode) clone.optimizable.getChild(clone.optimizable.getRoot(), 0), ((DefaultMutableTreeNode) clone.optimizable.getChild(clone.optimizable.getRoot(), 0)).getChildCount());
				clone.optimizable.insertNodeInto(new DefaultMutableTreeNode(seqC), (MutableTreeNode) clone.optimizable.getChild(clone.optimizable.getRoot(), 1), ((DefaultMutableTreeNode) clone.optimizable.getChild(clone.optimizable.getRoot(), 1)).getChildCount());
				
			}
			
			for(V v : this.plottedSeqs){
				clone.plottedSeqs.add(equivalence.get(v));
			}
			

			for(E e : this.edges.keySet()){
				V v1 = this.edges.get(e).getFirst();
				V v2 = this.edges.get(e).getSecond();
				clone.edges.put(e, new Pair<V>(equivalence.get(v1),equivalence.get(v2)));
			}
			
			
			for(E e : clone.edges.keySet()){
				Parameter<V,E> temp = new Parameter<V,E>(clone.getTemplateConcentration(e),0,100,e){
					
					
					
					@Override
					protected void init(){
						this.name = target.toString();
					}
					
					@Override
					protected void updateTarget(OligoGraph<V,E> graph) {
						clone.templateConcentrations.put((E)target, (Double) currentValue);
					}
				};
				final DefaultMutableTreeNode dmtn = new DefaultMutableTreeNode(temp);
				clone.optimizable.insertNodeInto(new DefaultMutableTreeNode(temp), (MutableTreeNode) clone.optimizable.getChild(clone.optimizable.getRoot(), 2), ((DefaultMutableTreeNode) clone.optimizable.getChild(clone.optimizable.getRoot(), 2)).getChildCount());
			}
			 return clone;
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
				
				
		
		return null;
	}
	
	public V getEquivalentVertex(V v){
		// Not great, nor scallable, but we can just go through all the sequences in the graph,
		// and return the first corresponding one.
		for(V seq : this.vertices.keySet()){
			if (seq.equals(v)){
				return seq;
			}
		}
		return null; //worst case senario
	}
}
